package com.mapforge.map.tools.tilewriting;

import java.awt.Point;
import java.util.HashSet;
import java.util.Stack;

import com.mapforge.map.Tile;
import com.mapforge.map.tilewriter.TileWriter;
import com.mapforge.map.tools.undo.TilePoint;
import com.mapforge.selection.Selection;
import com.mapforge.selection.TileSelection;

/** */
public class FloodFillTool extends AbstractTileWriterTool {

	private int tempx, tempy;
	private static final Point[] points = new Point[] { new Point(-1, 0),
			new Point(1, 0), new Point(0, 1), new Point(0, -1) };

	public FloodFillTool() {
		super("Flood fill");
	}

	@Override
	public void beginEdit() {
		super.beginEdit();
		if (getCurrentSelection() != null) {
			this.getCurrentSelection().clearSelection();
		}
	}

	@Override
	public void performEdit() {
	}

	@Override
	public void endEdit() {
		super.endEdit();
		this.validateState();
		this.makeSelection();
		Selection destination = this.getCurrentSelection();
		TileWriter tileWriter = this.getTileWriter();
		tileWriter.beginWrite();
		tileWriter.writeTiles(getTileSelection(), destination);
		tileWriter.endWrite();
	}

	@Override
	public synchronized TileSelection performSelection(Point anchorPoint,
			Point movingPoint) {
		Tile[][] data = this.getTileLayer().getData();
		Tile tile = data[movingPoint.y][movingPoint.x];
		TileSelection selection = new TileSelection();
		Stack<TilePoint> stack = new Stack<TilePoint>();
		HashSet<TilePoint> visitedPoints = new HashSet<TilePoint>();
		TilePoint p = new TilePoint(tile, movingPoint.x, movingPoint.y);

		selection.addPoint(p);
		visitedPoints.add(p);
		stack.push(p);

		while (!stack.isEmpty()) {
			p = stack.pop();
			for (int i = 0; i < points.length; i++) {
				TilePoint checkPoint = getValidPoint(points[i].x, points[i].y,
						p, tile);
				if (checkPoint != null && !visitedPoints.contains(checkPoint)) {
					selection.addPoint(checkPoint);
					visitedPoints.add(checkPoint);
					stack.push(checkPoint);
				}
			}
		}
		return selection;
	}

	private TilePoint getValidPoint(int dx, int dy, Point p, Tile origTile) {
		tempx = p.x + dx;
		tempy = p.y + dy;
		// check if point is within bounds
		if (tempx > -1 && tempy > -1
				&& tempx < getTileLayer().getTotalHorizontalTiles()
				&& tempy < getTileLayer().getTotalVerticalTiles()) {
			Tile tile = getTileLayer().getTileAt(tempx, tempy);
			// tiles are considered equal
			if (origTile.equals(tile)) {
				return new TilePoint(tile, tempx, tempy);
			}
			// tiles didn't match
			else {
				return null;
			}
		}
		// isn't within bounds
		else {
			return null;
		}
	}
}
